/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.util.core;

import jlibdiff.Diff;
import jlibdiff.Hunk;
import jlibdiff.HunkAdd;
import jlibdiff.HunkChange;
import jlibdiff.HunkDel;
import jlibdiff.HunkVisitor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.BufferedReader;
import java.io.StringReader;
import java.util.ArrayList;

/**
 * <p>Merge two strings of HTML or plain text</p>
 * <p/>
 * <p><a href="MergeUtil.java.html"><i>View Source</i></a></p>
 *
 * @author Andrey Grebnev <a href="mailto:andrey.grebnev@blandware.com">&lt;andrey.grebnev@blandware.com&gt;</a>
 * @version $Revision: 1.5 $ $Date: 2006/03/16 11:09:43 $
 */
public class MergeUtil {

	public static final int OLD_HTML = 0;
	public static final int NEW_HTML = 1;

	/**
	 * <p>Merges the HTML</p>
	 * <p>The changes will be emphasized using div with following styles: addHtml, oldHtml, newHtml, delHtml</p>
	 * <p>If some error is occured the text will be empty</p>
	 * <p>If htmls are identical, the old text will be as result</p>
     *
     * @param oldString Old HTML string
	 * @param newString New HTML string
	 * @return merged HTML
	 */
	public static String mergeHtml(String oldString, String newString) {
		Log log = LogFactory.getLog(MergeUtil.class);

		final ArrayList hunks = new ArrayList();
		BufferedReader oldReader = new BufferedReader(new StringReader(oldString));
		BufferedReader newReader = new BufferedReader(new StringReader(newString));
		StringBuffer mergedHtml = new StringBuffer();

		try {
			Diff diff = new Diff();
			diff.diffBuffer(oldReader, newReader);
			diff.accept(new HunkVisitor() {
				public void visitHunkAdd(HunkAdd hunkAdd) {
					hunks.add(hunkAdd);
				}

				public void visitHunkChange(HunkChange hunkChange) {
					hunks.add(hunkChange);
				}

				public void visitHunkDel(HunkDel hunkDel) {
					hunks.add(hunkDel);
				}
			});

			int position = 0;
			if ( hunks.size() != 0 ) {
				for ( int i = 0; i < hunks.size(); i++ ) {
					Hunk hunk = (Hunk) hunks.get(i);

					if ( hunk instanceof HunkAdd ) {
						mergedHtml.append(oldString.substring(position, getOffsetSOL(hunk.lowLine(OLD_HTML), oldString)))
						        .append("<div class=\"addHtml\">")
						        .append(newString.substring(getOffsetSOL(hunk.lowLine(NEW_HTML) - 1, newString), getOffsetEOL(hunk.highLine(NEW_HTML) - 1, newString)))
						        .append("</div>");
						position = getOffsetSOL(hunk.highLine(OLD_HTML), oldString);
					} else if ( hunk instanceof HunkChange ) {
						mergedHtml.append(oldString.substring(position, getOffsetSOL(hunk.lowLine(OLD_HTML) - 1, oldString)))
						        .append("<div class=\"oldHtml\">")
						        .append(oldString.substring(getOffsetSOL(hunk.lowLine(OLD_HTML) - 1, oldString), getOffsetEOL(hunk.highLine(OLD_HTML) - 1, oldString)))
						        .append("</div>\n")
						        .append("<div class=\"newHtml\">")
						        .append(newString.substring(getOffsetSOL(hunk.lowLine(NEW_HTML) - 1, newString), getOffsetEOL(hunk.highLine(NEW_HTML) - 1, newString)))
						        .append("</div>");
						position = getOffsetSOL(hunk.highLine(OLD_HTML), oldString);
					} else /* HunkDel */ {
						mergedHtml.append(oldString.substring(position, getOffsetSOL(hunk.lowLine(OLD_HTML) - 1, oldString)))
						        .append("<div class=\"delHtml\">")
						        .append(oldString.substring(getOffsetSOL(hunk.lowLine(OLD_HTML) - 1, oldString), getOffsetEOL(hunk.highLine(OLD_HTML) - 1, oldString)))
						        .append("</div>");
						position = getOffsetSOL(hunk.highLine(OLD_HTML), oldString);
					}
				}
				mergedHtml.append(oldString.substring(position));
			} else {
				//if identical
				mergedHtml.append(oldString);
			}

		} catch ( Exception ex ) {
			if ( log.isErrorEnabled() ) {
				log.error("Cannot merge html cause" + ex.getLocalizedMessage(), ex);
			}
		} finally {
			try {
				oldReader.close();
				newReader.close();
			} catch ( Exception ex2 ) {
				//do nothing
			}
		}

		return mergedHtml.toString();
	}

	/**
	 * <p>Merge the plain text specified in constructor.</p>
	 * <p>The changes will be emphasized using sequences of special symbols</p>
	 * <p>If some error is occured the text will be empty</p>
	 * <p>If texts are identical, the old text will be as result</p>
	 *
     * @param oldString old text
     * @param newString new text
	 * @return merged text
	 */
	public static String mergeText(String oldString, String newString) {
		Log log = LogFactory.getLog(MergeUtil.class);

		final ArrayList hunks = new ArrayList();
		BufferedReader oldReader = new BufferedReader(new StringReader(oldString));
		BufferedReader newReader = new BufferedReader(new StringReader(newString));
		StringBuffer mergedText = new StringBuffer();

		try {
			Diff diff = new Diff();
			diff.diffBuffer(oldReader, newReader);
			diff.accept(new HunkVisitor() {
				public void visitHunkAdd(HunkAdd hunkAdd) {
					hunks.add(hunkAdd);
				}

				public void visitHunkChange(HunkChange hunkChange) {
					hunks.add(hunkChange);
				}

				public void visitHunkDel(HunkDel hunkDel) {
					hunks.add(hunkDel);
				}
			});

			int position = 0;
			if ( hunks.size() != 0 ) {
				for ( int i = 0; i < hunks.size(); i++ ) {
					Hunk hunk = (Hunk) hunks.get(i);

					if ( hunk instanceof HunkAdd ) {
						mergedText.append(oldString.substring(position, getOffsetSOL(hunk.lowLine(OLD_HTML), oldString)))
						        .append(">>>>>>>>>>>>>>>>>>>>")
						        .append(newString.substring(getOffsetSOL(hunk.lowLine(NEW_HTML) - 1, newString), getOffsetEOL(hunk.highLine(NEW_HTML) - 1, newString)))
						        .append(">>>>>>>>>>>>>>>>>>>>");
						position = getOffsetSOL(hunk.highLine(OLD_HTML), oldString);
					} else if ( hunk instanceof HunkChange ) {
						mergedText.append(oldString.substring(position, getOffsetSOL(hunk.lowLine(OLD_HTML) - 1, oldString)))
						        .append("<<<<<<<<<<<<<<<<<<<<")
						        .append(oldString.substring(getOffsetSOL(hunk.lowLine(OLD_HTML) - 1, oldString), getOffsetEOL(hunk.highLine(OLD_HTML) - 1, oldString)))
						        .append("\n<<<<<<<<<<<<<<<<<<<<\n")
						        .append(">>>>>>>>>>>>>>>>>>>>")
						        .append(newString.substring(getOffsetSOL(hunk.lowLine(NEW_HTML) - 1, newString), getOffsetEOL(hunk.highLine(NEW_HTML) - 1, newString)))
						        .append("\n>>>>>>>>>>>>>>>>>>>>");
						position = getOffsetSOL(hunk.highLine(OLD_HTML), oldString);
					} else /* HunkDel */ {
						mergedText.append(oldString.substring(position, getOffsetSOL(hunk.lowLine(OLD_HTML) - 1, oldString)))
						        .append("--------------------")
						        .append(oldString.substring(getOffsetSOL(hunk.lowLine(OLD_HTML) - 1, oldString), getOffsetEOL(hunk.highLine(OLD_HTML) - 1, oldString)))
						        .append("--------------------");
						position = getOffsetSOL(hunk.highLine(OLD_HTML), oldString);
					}
				}
				mergedText.append(oldString.substring(position));
			} else {
				//if identical
				mergedText.append(oldString);
			}

		} catch ( Exception ex ) {
			if ( log.isErrorEnabled() ) {
				log.error("Cannot merge text cause" + ex.getLocalizedMessage(), ex);
			}
		} finally {
			try {
				oldReader.close();
				newReader.close();
			} catch ( Exception ex2 ) {
				//do nothing
			}
		}

		return mergedText.toString();
	}


	/**
	 * Returns offset of start of line
	 *
	 * @param linenumber
	 * @param buffer
	 * @return offset of the start of the line or 0 is the first is the first line
	 */
	protected static int getOffsetSOL(int linenumber, String buffer) {
		if ( linenumber == 0 ) {
			return 0;
		} else {
			int counter = 0;
			int offset = -1;
			int tmp = -1;
			while ( counter < linenumber ) {
				offset++;
				counter++;
				tmp = buffer.indexOf('\n', offset);
				if ( tmp == -1 ) {
					return buffer.length();
				}
				offset = tmp;
			}
			return offset;
		}
	}

	/**
	 * Return offset of the end of the line
	 *
	 * @param linenumber
	 * @param buffer
	 * @return offset
	 */
	protected static int getOffsetEOL(int linenumber, String buffer) {
		int offsetSOL = getOffsetSOL(linenumber, buffer);
		int offsetEOL = buffer.indexOf('\n', offsetSOL + 1);
		if ( offsetEOL == -1 ) {
			return buffer.length();
		} else {
			return offsetEOL;
		}
	}

/*

    public static void main(String[] args) {
        try {
            String oldHtml = readFile("C:\\TOOLS\\java\\jlibdiff\\1.txt");
            String newHtml = readFile("C:\\TOOLS\\java\\jlibdiff\\2.txt");
            String mergedHtml = MergeUtil.mergeText(oldHtml, newHtml);
            System.out.println(mergedHtml);
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }

    protected static String readFile(String fileName) throws Exception {
        FileInputStream inputStream = new FileInputStream(fileName);
        int fileSize = inputStream.available();
        byte[] fileContent = new byte[fileSize];
        inputStream.read(fileContent, 0, fileSize);
        inputStream.close();
        return new String(fileContent, "UTF-8");
    }
*/

}
